﻿using H.Cmd;
using H.PK;
using ILMerging;
using Microsoft.Win32;
using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security;
using System.Threading;
using System.Windows.Forms;

namespace H.Compiler
{
    internal class Program
    {
        private static void DisplayHelper()
        {
            Console.WriteLine("Usage: <exe> /hpk [/debug] [/secure  [/exclude <expression>]] <sourceFile> <destinationFileOrDirectory> [entryName]");
            Console.WriteLine("Usage: <exe> /happ [/debug] [/secure  [/exclude <expression>]] <sourceFile> <destinationFileOrDirectory> <entryName>");
            Console.WriteLine("Usage: <exe> /merge [/debug] [/secure  [/exclude <expression>]] <destinationFile> <sourceFile1> <sourceFile2> [sourceFile3] ...");
            Console.WriteLine("Usage: <exe> /secure <destinationFile> <sourceFile>");
            Console.WriteLine("Usage: <exe> /copy <destinationDirectory> <sourceFileOrDirectory1> [<sourceFileOrDirectory2>] ...");
            Console.WriteLine("Usage: <exe> /clip <sourceFile> [<sourceFile2>] ...");
            Console.WriteLine("Usage: <exe> /pack <cfgFile>");
            Console.WriteLine("Usage: <exe> /reg [app|log]");
            Console.WriteLine("Usage: <exe> /kill [...procName]");
        }

        [STAThread]
        public static int Main(string[] args)
        {
            Arguments arguments = new Arguments(args);
            if (arguments.Count == 0)
            {
                DisplayHelper();
                return 0;
            }
            int maxIndex = 1;
            //更新最后一个必填项的开始位置
            int updateMaxIndex(int index, int appendCount)
            {
                if (index != -1)
                {
                    maxIndex = Math.Max(index + appendCount + 1, maxIndex);
                }
                return index;
            }
            switch (arguments[0])
            {
                case "/hpk":
                    {
                        bool isDebug = updateMaxIndex(arguments.IndexOfOption("/debug", 1, true), 0) != -1;
                        bool isSecure = updateMaxIndex(arguments.IndexOfOption("/secure", 1, true), 0) != -1;
                        string exclude = null;
                        int tmpIndex = updateMaxIndex(arguments.IndexOfOption("/exclude", 1, true), 1);
                        if (tmpIndex != -1)
                        {
                            exclude = arguments[tmpIndex + 1];
                        }

                        if (arguments.Count - maxIndex < 2)
                        {
                            Console.Error.WriteLine("参数个数不足");
                            return 2;
                        }

                        string entryName = maxIndex + 2 < arguments.Count ? arguments[maxIndex + 2] : "";
                        string srcFile = arguments[maxIndex];
                        string dstFile = arguments[maxIndex + 1];
                        if (!File.Exists(srcFile))
                        {
                            Console.WriteLine(srcFile + " 不存在");
                            return 1;
                        }

                        if (isSecure)
                        {
                            string fileNameWithoutExt = Path.GetFileNameWithoutExtension(srcFile);
                            string secureDir = Path.Combine(Path.GetDirectoryName(srcFile), $"{fileNameWithoutExt}_Secure");

                            FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(srcFile);
                            string nrmapFile = Path.Combine(secureDir, $"{fileNameWithoutExt}_{fileVersionInfo.FileVersion}.nrmap");

                            if (File.GetLastWriteTime(Path.Combine(secureDir, Path.GetFileName(srcFile))) < File.GetLastWriteTime(srcFile))
                            {
                                if (File.Exists(nrmapFile))
                                {
                                    File.Delete(nrmapFile);
                                }

                                //库文件加密配置
                                var process = Process.Start(
                                 new ProcessStartInfo(
                                     Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "lib\\Reactor.exe"),
                                     $"-file \"{srcFile}\" -q"
                                        + " -visualstyles 0 -antitamp 1 -resourceencryption 1 -suppressildasm 0 -merge_namespaces 1 -mapping_file 1 -short_strings 1 -stealth_mode 1 -all_params 1 -exclude_serializable_types 1 -unprintable_characters 1 -obfuscate_public_types 1"
                                        + (exclude != null ? $" -exclusion_rules \"{exclude}\"" : string.Empty)
                                 )
                                 { WindowStyle = ProcessWindowStyle.Hidden }
                                 );
                                process.WaitForExit();
                                Console.WriteLine("HPK加密返回值:" + process.ExitCode);
                            }
                            entryName = TranslateTypeName(nrmapFile, entryName);
                            srcFile = $"{secureDir}\\{ Path.GetFileName(srcFile)}";
                        }

                        if (string.IsNullOrEmpty(Path.GetExtension(dstFile)))
                        {
                            Directory.CreateDirectory(dstFile);
                            dstFile = Path.Combine(dstFile, Path.GetFileName(srcFile));
                        }
                        else
                        {
                            Directory.CreateDirectory(Path.GetDirectoryName(dstFile));
                        }

                        //修正文件后缀名
                        dstFile = Path.ChangeExtension(dstFile, "hpk");

                        if (!File.Exists(dstFile) || File.GetLastWriteTime(srcFile) > File.GetLastWriteTime(dstFile))
                        {
                            new HPackInfo(srcFile, isDebug).Write(dstFile);
                            Console.WriteLine("已生成: " + dstFile);
                        }
                        else
                        {
                            Console.WriteLine(dstFile + " 已是最新版本");
                        }
                        return 0;
                    }
                case "/happ":
                    {
                        bool isDebug = updateMaxIndex(arguments.IndexOfOption("/debug", 1, true), 0) != -1;
                        bool isSecure = updateMaxIndex(arguments.IndexOfOption("/secure", 1, true), 0) != -1;
                        string exclude = null;

                        int tmpIndex = updateMaxIndex(arguments.IndexOfOption("/exclude", 1, true), 1);
                        if (tmpIndex != -1)
                        {
                            exclude = arguments[tmpIndex + 1];
                        }


                        if (arguments.Count - maxIndex < 2)
                        {
                            Console.Error.WriteLine("参数个数不足");
                            return 2;
                        }

                        string entryName = maxIndex + 2 < arguments.Count ? arguments[maxIndex + 2] : "";
                        string srcFile = arguments[maxIndex];
                        string dstFile = arguments[maxIndex + 1];
                        if (!File.Exists(srcFile))
                        {
                            Console.WriteLine(srcFile + " 不存在");
                            return 1;
                        }

                        if (isSecure)
                        {
                            string fileNameWithoutExt = Path.GetFileNameWithoutExtension(srcFile);
                            string secureDir = Path.Combine(Path.GetDirectoryName(srcFile), $"{fileNameWithoutExt}_Secure");

                            FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(srcFile);
                            string nrmapFile = Path.Combine(secureDir, $"{fileNameWithoutExt}_{fileVersionInfo.FileVersion}.nrmap");

                            if (File.GetLastWriteTime(Path.Combine(secureDir, Path.GetFileName(srcFile))) < File.GetLastWriteTime(srcFile))
                            {
                                if (File.Exists(nrmapFile))
                                {
                                    File.Delete(nrmapFile);
                                }

                                //应用程序加密配置
                                var process = Process.Start(
                                    new ProcessStartInfo(
                                        Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "lib\\Reactor.exe"),
                                        $"-file \"{srcFile}\" -q" +
                                        " -merge_attributes 0 -internalization 1 -prejit 1 -resourceencryption 1 -suppressildasm 0 -merge_namespaces 1 -mapping_file 1 -short_strings 1 -stealth_mode 1 -all_params 1 -exclude_serializable_types 1 -unprintable_characters 1 -obfuscate_public_types 1"
                                        + (exclude != null ? $" -exclusion_rules \"{exclude}\"" : string.Empty)
                                    )
                                    { WindowStyle = ProcessWindowStyle.Hidden }
                                    );
                                process.WaitForExit();
                                Console.WriteLine("HAPP加密返回值:" + process.ExitCode);
                            }
                            entryName = TranslateTypeName(nrmapFile, entryName);
                            srcFile = $"{secureDir}\\{ Path.GetFileName(srcFile)}";
                        }

                        if (string.IsNullOrEmpty(Path.GetExtension(dstFile)))
                        {
                            Directory.CreateDirectory(dstFile);
                            dstFile = Path.Combine(dstFile, Path.GetFileName(srcFile));
                        }
                        else
                        {
                            Directory.CreateDirectory(Path.GetDirectoryName(dstFile));
                        }

                        //修正文件后缀名
                        dstFile = Path.ChangeExtension(dstFile, "app");

                        if (!File.Exists(dstFile) || File.GetLastWriteTime(srcFile) > File.GetLastWriteTime(dstFile))
                        {
                            new HPackInfo(srcFile, isDebug).Write(dstFile);
                            Console.WriteLine("已生成: " + dstFile);
                        }
                        else
                        {
                            Console.WriteLine(dstFile + " 已是最新版本");
                        }
                        return 0;
                    }
                case "/merge":
                    {
                        bool isDebug = updateMaxIndex(arguments.IndexOfOption("/debug", 1, true), 0) != -1;
                        bool isSecure = updateMaxIndex(arguments.IndexOfOption("/secure", 1, true), 0) != -1;
                        string exclude = null;

                        int tmpIndex = updateMaxIndex(arguments.IndexOfOption("/exclude", 1, true), 1);
                        if (tmpIndex != -1)
                        {
                            exclude = arguments[tmpIndex + 1];
                        }


                        if (arguments.Count - maxIndex < 3)
                        {
                            Console.Error.WriteLine("参数个数不足");
                            return 2;
                        }

                        string entryName = maxIndex + 2 < arguments.Count ? arguments[maxIndex + 2] : "";
                        string dstFile = arguments[maxIndex];
                        string srcFile = arguments[maxIndex + 1];


                        if (isSecure)
                        {
                            string mergeDir = Path.Combine(Path.GetDirectoryName(srcFile), Path.GetFileNameWithoutExtension(srcFile) + "_merge");
                            Directory.CreateDirectory(mergeDir);
                            dstFile = Path.Combine(mergeDir, Path.GetFileName(dstFile));
                        }

                        string[] srcFiles = args.Skip(maxIndex + 1).ToArray();
                        if (File.Exists(dstFile))
                        {
                            DateTime dstFileWTime = File.GetLastWriteTime(dstFile);
                            if (srcFiles.All(s => File.GetLastWriteTime(s) <= dstFileWTime))
                            {
                                Console.WriteLine(dstFile + " 已是最新版本");
                                return 0;
                            }
                        }

                        ILMerge iLMerge = new ILMerge();
                        iLMerge.SetInputAssemblies(srcFiles);
                        iLMerge.OutputFile = dstFile;
                        iLMerge.SetTargetPlatform("v4", "");
                        iLMerge.TargetKind = ILMerge.Kind.SameAsPrimaryAssembly;
                        iLMerge.DebugInfo = isDebug;
                        iLMerge.Merge();

                        if (isSecure)
                        {
                            srcFile = dstFile;
                            dstFile = arguments[maxIndex];

                            //应用程序加密配置
                            Process.Start(
                                new ProcessStartInfo(
                                    Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "lib\\Reactor.exe"),
                                    $"-file \"{srcFile}\" -q -targetfile \"{dstFile}\" -suppressildasm 0 -merge_namespaces 1 -all_params 1 -unprintable_characters 1 -obfuscate_public_types 1"
                                        + (exclude != null ? $" -exclusion_rules \"{exclude}\"" : string.Empty)
                                )
                                { WindowStyle = ProcessWindowStyle.Hidden }
                                ).WaitForExit();
                            Console.WriteLine("已生成: " + dstFile);
                        }
                        else
                        {
                            Console.WriteLine(dstFile + " 已是最新版本");
                        }
                        return 0;
                    }
                case "/secure":
                    {
                        Console.WriteLine("未实现");
                        return 0;
                    }
                case "/copy":
                    {
                        string dstDir = arguments[1];
                        string[] srcPath = arguments.GetOptions(2);

                        Directory.CreateDirectory(dstDir);
                        foreach (string path in srcPath)
                        {
                            string targetFile = Path.Combine(dstDir, Path.GetFileName(path));

                            if (File.Exists(path))
                            {
                                if (!File.Exists(targetFile) || File.GetLastWriteTime(targetFile) < File.GetLastWriteTime(path))
                                {
                                    File.Copy(path, targetFile, true);
                                    Console.WriteLine("已复制: " + targetFile);
                                }
                            }
                            else if (Directory.Exists(path))
                            {
                                CopyDirectory(dstDir, path);
                            }
                            else
                            {
                                Console.Error.WriteLine("文件不存在:" + Path.GetFullPath(path));
                            }
                        }
                        return 0;
                    }
                case "/clip":
                    {
                        StringCollection sc = new StringCollection();
                        sc.AddRange(arguments.GetOptions(1));
                        Clipboard.SetFileDropList(sc);
                        return 0;
                    }
                case "/reg":
                    {
                        try
                        {
                            if (arguments.Count >= 2)
                            {
                                string appBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
                                string exeFile = Path.Combine(appBase, "hrt.exe");
                                string exe32File = Path.Combine(appBase, "hrt32.exe");
                                switch (arguments[1])
                                {
                                    case "app":
                                        RegClass("happFile",
                                                 "Run With HRT",
                                                 exeFile + " %1",
                                                 exeFile
                                                 );
                                        RegClass("happFile",
                                                 "Run With HRT(强制32位)",
                                                 exe32File + " %1",
                                                 exe32File);
                                        RegType(".app", "happFile");
                                        Console.WriteLine("已经注册.app后缀");
                                        return 0;
                                    case "log":
                                        RegClass("logFile",
                                                 "Open With LogViewer(HRT)",
                                                 $"{exeFile} \"{Path.Combine(appBase, "app\\LogViewer.app")}\" %1",
                                                 exeFile
                                                 );
                                        RegType(".log", "logFile");
                                        Console.WriteLine("已经注册.app后缀");
                                        return 0;
                                    default:
                                        Console.Error.WriteLine("未能注册此类型");
                                        return 3;
                                }
                            }
                            else
                            {
                                Console.Error.WriteLine("参数不足");
                                return 2;
                            }
                        }
                        catch (SecurityException)
                        {
                            Console.Error.WriteLine("请使用超级管理员执行此操作");
                            return 3;
                        }
                    }
                case "/kill":
                    {
                        foreach (string arg in args.Skip(1))
                        {
                            foreach (Process process in ProcessGetByName(arg))
                            {
                                process.Kill();
                            }
                        }
                        return 0;
                    }
                default:
                    {
                        Console.Error.WriteLine("未知指令");
                        return 1;
                    }
            }
        }

        /// <summary>
        /// 根据进程名称查找进程
        /// </summary>
        /// <param name="procName"></param>
        /// <returns></returns>
        public static Process[] ProcessGetByName(string procName)
        {
            if (procName.Contains(".exe"))
            {
                return Process.GetProcessesByName(procName.Substring(0, procName.IndexOf(".exe")));
            }
            else
            {
                return Process.GetProcessesByName(procName);
            }
        }

        /// <summary>
        /// 注册一种文件
        /// </summary>
        /// <param name="name"></param>
        /// <param name="label"></param>
        /// <param name="command"></param>
        /// <param name="icon"></param>
        private static void RegClass(string name, string label, string command, string icon)
        {
            RegistryKey registryKey = RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Default);
            RegistryKey typeKey = registryKey.OpenSubKey(name, true) ?? registryKey.CreateSubKey(name);
            RegistryKey shellKey = typeKey.CreateSubKey("shell");
            RegistryKey shellKey2 = shellKey.CreateSubKey(label ?? "Open With HRT");

            shellKey2.SetValue("Icon", icon);
            shellKey2.CreateSubKey("command").SetValue("", command);
        }

        /// <summary>
        /// 注册一个后缀名为某种文件
        /// </summary>
        private static void RegType(string suffixName, string className)
        {
            RegistryKey registryKey = RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Default);
            (registryKey.OpenSubKey(suffixName, true) ?? registryKey.CreateSubKey(suffixName)).SetValue("", className);
        }

        /// <summary>
        /// 获取与DLL对应的入口点地址的类名
        /// </summary>
        /// <param name="nrmapFile"></param>
        /// <param name="srcType"></param>
        /// <returns></returns>
        private static string TranslateTypeName(string nrmapFile, string srcType)
        {
            if (string.IsNullOrEmpty(srcType))
            {
                return srcType;
            }
            else if (!File.Exists(nrmapFile))
            {
                Thread.Sleep(1000);
                if (!File.Exists(nrmapFile))
                {
                    return srcType;
                }
            }
            using (StreamReader sr = new StreamReader(new FileStream(nrmapFile, FileMode.Open, FileAccess.Read)))
            {
                string line;
                while ((line = sr.ReadLine()) != null)
                {
                    if (line == "<<type>>")
                    {
                        string dstName = sr.ReadLine();
                        if ((line = sr.ReadLine()) == srcType)
                        {
                            return dstName;
                        }
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// 递归复制文件夹
        /// </summary>
        /// <param name="dstDir"></param>
        /// <param name="srcDir"></param>
        private static void CopyDirectory(string dstDir, string srcDir)
        {
            dstDir = Path.Combine(dstDir, Path.GetFileName(srcDir));
            if (!Directory.Exists(dstDir))
            {
                Directory.CreateDirectory(dstDir);
            }
            DirectoryInfo directoryInfo = new DirectoryInfo(srcDir);
            if (directoryInfo.Exists)
            {
                foreach (FileInfo file in directoryInfo.GetFiles())
                {
                    string targetFile = Path.Combine(dstDir, file.Name);
                    if (File.GetLastWriteTime(targetFile) < file.LastWriteTime)
                    {
                        file.CopyTo(targetFile, true);
                        Console.WriteLine("已复制: " + targetFile);
                    }
                }
                foreach (DirectoryInfo dir in directoryInfo.GetDirectories())
                {
                    CopyDirectory(dstDir, dir.FullName);
                }
            }
        }
    }
}
